/*
  S.M.A.C.K - An operating system kernel
  Copyright (C) 2010,2011 Mattias Holm and Kristian Rietveld
  For licensing and a full list of authors of the kernel, see the files
  COPYING and AUTHORS.
*/

#include <elf.h>
#include <vm.h>
#include <string.h>
#include <vfs/vfs.h>
#include <stdbool.h>
#include <assert.h>
#include <bittools.h>

bool
elf_probe(const char *filename)
{
  char buf[8];
  vfs_vnode_t *vnode = vfs_getnode(filename);

  if (!vnode)
    return false;

  vfs_file_t *fd = vfs_open(vnode);
  if (!fd)
    return false;

  if (vfs_read(fd, buf, 8) < 0)
    {
      vfs_close(fd);
      return false;
    }

  vfs_close(fd);

  if (buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F')
    return true;

  return false;
}

static void *
elf_load_file(const char *filename)
{
  vfs_vnode_t *vnode = vfs_getnode(filename);
  vfs_file_t *fd = vfs_open(vnode);

  if (fd == NULL)
    return NULL;

  /* FIXME: It is bad copying this much data into the kernel heap. We
   * should do something else.  For example load or copy the data per
   * segment in elf_load().
   */
  void *data = vm_map(vm_get_kernel_map(VM_REG_KERNEL_HEAP), VM_SUPER_RW, 0,
                      (vnode->size / 4096 + 1) * 4096);
  vfs_read(fd, data, vnode->size);
  vfs_close(fd);

  return data;
}

void*
elf_load(proc_t *proc, const char *filename)
{
  void *data;

  data = elf_load_file(filename);
  if (data == 0x0)
    {
      printf ("ELF loader: file '%s' not found.\r\n", filename);
      return NULL;
    }

  const Elf32_Ehdr *elf = data;

  assert(proc != NULL && "must load to user process");
  if (!(elf->e_ident[EI_MAG0] == 0x7f
        && elf->e_ident[EI_MAG1] == 'E'
        && elf->e_ident[EI_MAG2] == 'L'
        && elf->e_ident[EI_MAG3] == 'F')) {
    goto fail; // Magic number incorrect
  }

  if (elf->e_ident[EI_CLASS] != ELFCLASS32) return 0; // Not a 32 bit elf file
  if (elf->e_ident[EI_DATA] != ELFDATA2LSB) return 0; // Not little endian
  if (elf->e_ident[EI_VERSION] != EV_CURRENT) return 0; // Unknown header version

  if (elf->e_type == ET_EXEC
      && elf->e_machine != EM_ARM
      && elf->e_version != EV_CURRENT) {
    goto fail; // Not an executable arm file of elf version 1
  }

  if (elf->e_phoff == 0) {
    goto fail; // Not a proper elf executable (must have program header)
  }

  // We are now dealing with an elf file of the appropriate architecture

  // We could do more checks, but let's limit to these for now
  // TODO: Handle thread local storage segments
  Elf32_Phdr *progheader = (Elf32_Phdr*)((uintptr_t)elf + (uintptr_t)elf->e_phoff);
  for (int i = 0 ; i < elf->e_shnum ; i++) {
    if (progheader[i].p_type == PT_LOAD) {
      uint32_t flags = VM_SUPER_RW;
      if (progheader[i].p_flags & PF_R) flags |= VM_USER_READ;
      if (progheader[i].p_flags & PF_W) flags |= VM_USER_WRITE;
      if (progheader[i].p_flags & PF_X)
        {
          flags |= VM_USER_EXEC;

          /* FIXME: We need to include VM_SUPER_EXEC for ARM.  This is
           * platform-specific and needs to be figured out/corrected.
           */
          flags |= VM_SUPER_EXEC;
        }

      void *segdata = (void*)elf + progheader[i].p_offset;
      // vm map...
      void *addr = vm_map(&proc->vm_map, flags, progheader[i].p_vaddr,
                          align_va(progheader[i].p_memsz, 4096));
      assert(addr && "oups... invalid address in elf load, check your linker script");
      memcpy(addr, segdata, progheader[i].p_filesz);

      if (progheader[i].p_filesz != progheader[i].p_memsz) {
        // TODO: vm_map should do zero page allocation
        memset((char*)addr + progheader[i].p_filesz, 0,
               progheader[i].p_memsz - progheader[i].p_filesz);
      }
    }
  }

  void *entrypoint = (void *)elf->e_entry;
  vm_unmap(vm_get_kernel_map(VM_REG_KERNEL_HEAP), data);
  return entrypoint;

fail:
  vm_unmap(vm_get_kernel_map(VM_REG_KERNEL_HEAP), data);
  return 0;
}
